package gl.textures;

import java.util.ArrayList;
import java.util.HashMap;

import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;
import javax.microedition.khronos.opengles.GL11Ext;

import util.HasDebugInformation;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.opengl.GLUtils;
import android.util.Log;

public class TextureManager implements HasDebugInformation {

	private static final String LOG_TAG = "Texture Manager";

	private static TextureManager instance = new TextureManager();

	/**
	 * TODO why am i using ArrayList here...
	 */
	private ArrayList<Texture> newTexturesToLoad;
	private int textureArrayOffset = 0;
	private int[] textureArray = new int[40];
	private HashMap<String, Texture> myTextureMap;

	// TODO reuse of same texture not possible this way
	public void addTexture(TexturedRenderData target, Bitmap bitmap,
			String textureName) {

		Texture t = loadTextureFromMap(textureName);

		if (t == null) {
			t = new Texture(target, bitmap, textureName);
			Log.d(LOG_TAG, "Texture for " + textureName
					+ " not jet added, so it will get a new texture id");
			addTextureToMap(t);
			if (newTexturesToLoad == null) {
				Log.i(LOG_TAG,
						"Texture Manage never used before, now its initialized (This message should only appear once!)");
				newTexturesToLoad = new ArrayList<Texture>();
			}
			newTexturesToLoad.add(t);
		} else {
			Log.d(LOG_TAG, "Texture for " + textureName
					+ " already added, so it will get the same texture id");
			t.addRenderData(target);
		}

	}

	private Texture loadTextureFromMap(String textureName) {
		if (myTextureMap == null)
			return null;
		return myTextureMap.get(textureName);
	}

	public void updateTextures(GL10 gl) {
		if (newTexturesToLoad != null && newTexturesToLoad.size() > 0) {
			try {
				// generate and store id numbers in textureArray:
				gl.glGenTextures(newTexturesToLoad.size(), textureArray,
						textureArrayOffset);
				int newtextureArrayOffset = newTexturesToLoad.size();

				for (int i = 0; i < newTexturesToLoad.size(); i++) {

					Texture t = newTexturesToLoad.get(i);
					int newTextureId = textureArray[textureArrayOffset + i];

					t.idArrived(newTextureId);

					gl.glBindTexture(GL10.GL_TEXTURE_2D, newTextureId);

					gl.glTexParameterf(GL10.GL_TEXTURE_2D,
							GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
					gl.glTexParameterf(GL10.GL_TEXTURE_2D,
							GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
					gl.glTexParameterf(GL10.GL_TEXTURE_2D,
							GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
					gl.glTexParameterf(GL10.GL_TEXTURE_2D,
							GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);

					gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,
							GL10.GL_REPLACE);

					GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, t.getImage(), 0);

					int[] mCropWorkspace = new int[4];
					mCropWorkspace[0] = 0;
					mCropWorkspace[1] = t.getImage().getHeight();
					mCropWorkspace[2] = t.getImage().getWidth();
					mCropWorkspace[3] = -t.getImage().getHeight();

					// TODO maybe not working on any phone because using GL11?
					((GL11) gl)
							.glTexParameteriv(GL10.GL_TEXTURE_2D,
									GL11Ext.GL_TEXTURE_CROP_RECT_OES,
									mCropWorkspace, 0);

					t.recycleImage();

					int error = gl.glGetError();
					if (error != GL10.GL_NO_ERROR) {
						Log.e("SpriteMethodTest", "Texture Load GLError: "
								+ error);
					}

				}
				textureArrayOffset = newtextureArrayOffset;
				newTexturesToLoad.clear();
			} catch (Exception e) {
				showDebugInformation();
				e.printStackTrace();
			}
		}
	}

	private void addTextureToMap(Texture t) {
		if (myTextureMap == null)
			myTextureMap = new HashMap<String, Texture>();
		myTextureMap.put(t.getName(), t);
	}

	public static TextureManager getInstance() {
		return instance;
	}

	/**
	 * its important that the used textures have a size powered 2 (2,4,8,16,32..
	 * x 2,4,8..) so resize the bitmap if it has not the correct size
	 * 
	 * @param b
	 * @return
	 */
	public Bitmap resizeBitmapIfNecessary(Bitmap b) {
		int height = b.getHeight();
		int width = b.getWidth();
		int newHeight = getNextPowerOfTwoValue(height);
		int newWidth = getNextPowerOfTwoValue(width);
		if ((height != newHeight) || (width != newWidth)) {
			Log.v(LOG_TAG, "   > Need to resize bitmap: old height=" + height
					+ ", old width=" + width + ", new height=" + newHeight
					+ ", new width=" + newWidth);
			return resizeBitmap(b, newHeight, newWidth);
		}
		return b;
	}

	private Bitmap resizeBitmap(Bitmap bitmap, int newHeight, int newWidth) {
		int width = bitmap.getWidth();
		int height = bitmap.getHeight();
		float scaleWidth = ((float) newWidth) / width;
		float scaleHeight = ((float) newHeight) / height;
		Matrix matrix = new Matrix();
		matrix.postScale(scaleWidth, scaleHeight);
		return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
	}

	public static int getNextPowerOfTwoValue(double x) {
		/*
		 * calc log2(x) (log2(x) can be calculated with log(x)/log(2)) and get
		 * the next bigger integer value. then calc 2^this value
		 */
		double x2 = Math.pow(2, Math.floor(Math.log(x) / Math.log(2)) + 1);
		if (x2 != x) {
			return (int) x2;
		}
		return (int) x;
	}

	@Override
	public void showDebugInformation() {
		Log.i(LOG_TAG, "Debug infos about the Texture Manager:");
		Log.i(LOG_TAG, "   > newTexturesToLoad" + newTexturesToLoad);
		Log.i(LOG_TAG, "   > textureArray.length" + textureArray.length);
		Log.i(LOG_TAG, "   > textureArrayOffset" + textureArrayOffset);
		Log.i(LOG_TAG, "   > myTextureMap" + myTextureMap);
	}

	public static void resetInstance() {
		instance = new TextureManager();
	}

}
